﻿using Abp.UI;
using Newtonsoft.Json;
using SingleUserMall.Authorization.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web.Configuration;
using System.Xml.Linq;
using Senparc.Weixin.MP;
using Senparc.Weixin.MP.TenPayLibV3;
using Aop.Api.Util;
using System.IO;

namespace Pay.WxPay
{
    /// <summary>
    /// 微信支付
    /// </summary>
    public class WxPayContext
    {
        /** 默认编码字符集 */
        private static string DEFAULT_CHARSET = "UTF-8";
        //微信app支付单例
        private static WxPayContext instance = null;
        //微信公众号支付单例
        private static WxPayContext h5Instance = null;
        public static WxPayContext Instance
        {
            get
            {
                if (instance == null)
                    instance = new WxPayContext(
                        WebConfigurationManager.AppSettings["wxpay_appid"],
                        WebConfigurationManager.AppSettings["wxpay_appsecret"],
                        WebConfigurationManager.AppSettings["wxpay_mch_id"],
                        WebConfigurationManager.AppSettings["wxpay_api_key"],
                        WebConfigurationManager.AppSettings["wxpay_cert_path"]);
                return instance;
            }
        }
        public static WxPayContext H5Instance
        {
            get
            {
                if (h5Instance == null)
                    h5Instance = new WxPayContext(
                WebConfigurationManager.AppSettings["wxpay_h5_appid"],
                WebConfigurationManager.AppSettings["wxpay_h5_appsecret"],
                WebConfigurationManager.AppSettings["wxpay_h5_mch_id"],
                WebConfigurationManager.AppSettings["wxpay_h5_api_key"],
                WebConfigurationManager.AppSettings["wxpay_h5_cert_path"]
                );
                return h5Instance;
            }
        }
        public WxPayContext(string _appid, string _appsecret, string _mch_id, string _api_key, string _cert_path)
        {
            appid = _appid;
            appsecret = _appsecret;
            mch_id = _mch_id;
            api_key = _api_key;
            cert_path = _cert_path;
            pub_key = WebConfigurationManager.AppSettings["wxpay_pub_key"];
            notify_url = WebConfigurationManager.AppSettings["wxpay_notify_url"];
            server_ip = WebConfigurationManager.AppSettings["server_ip"];
        }
        /// <summary>
        /// 应用id
        /// </summary>
        private string appid { get; set; }
        /// <summary>
        /// 商户号
        /// </summary>
        private string mch_id { get; set; }
        /// <summary>
        /// 通知url
        /// </summary>
        private string notify_url { get; set; }
        /// <summary>
        /// 服务器ip
        /// </summary>
        private string server_ip { get; set; }
        /// <summary>
        /// 公众号appsecret
        /// </summary>
        private string appsecret { get; set; }
        /// <summary>
        /// 支付密钥
        /// </summary>
        private string api_key { get; set; }
        /// <summary>
        /// 公钥
        /// </summary>
        private string pub_key { get; set; }
        /// <summary>
        /// 证书地址
        /// </summary>
        private string cert_path { get; set; }
        /// <summary>
        /// 获取appid
        /// </summary>
        /// <returns></returns>
        public string GetAppId()
        {
            return appid;
        }
        /// <summary>
        /// 获取appSecret
        /// </summary>
        /// <returns></returns>
        public string GetAppSecret()
        {
            return appsecret;
        }
        private string SignToXml(Dictionary<string, string> array)
        {
            //签名
            var sign = Sign(array);
            array.Add("sign", sign);
            var xml = new StringBuilder();
            xml.Append("<xml>");
            foreach (var i in array)
            {
                xml.AppendFormat("<{0}><![CDATA[{1}]]></{0}>", i.Key, i.Value);
            }
            xml.Append("</xml>");
            return xml.ToString();
        }
        private string Sign(Dictionary<string, string> array)
        {
            array = array.OrderBy(i => i.Key).ToDictionary(i => i.Key, i => i.Value);
            var kepairArray = array.Where(i => !string.IsNullOrEmpty(i.Value)).Select(i => string.Format("{0}={1}", i.Key, i.Value));
            //加入密钥并生成签名字符串
            var signstr = string.Format("{0}&{1}={2}", string.Join("&", kepairArray), "key", api_key);
            //签名
            var sign = MD5(signstr);
            return sign;
        }
        /// <summary>
        /// 下单参数
        /// </summary>
        /// <param name="out_trade_no">外部单号</param>
        /// <param name="body">主题</param>
        /// <param name="detail">详情</param>
        /// <param name="totalfee">总额</param>
        /// <param name="tradetype">交易方式：JSAPI 公众号支付，NATIVE 扫码支付，APP APP支付</param>
        /// <param name="openid">openid</param>
        /// <returns></returns>
        public string PreOrder(string out_trade_no, string body, string detail, string totalfee, string tradetype = "JSAPI", string openid = "")
        {
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appid", appid);
            array.Add("body", body);
            array.Add("detail", detail);
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("notify_url", notify_url);
            if (tradetype == "JSAPI")
            {
                array.Add("openid", openid);
            }
            array.Add("out_trade_no", out_trade_no);
            array.Add("spbill_create_ip", server_ip);//终端ip
            array.Add("total_fee", ((int)(decimal.Parse(totalfee) * 100)).ToString());//单位分
            array.Add("trade_type", tradetype);
            //app下单
            //return xml.ToString();
            //后台下单
            return UnifiedOrder(SignToXml(array), tradetype);
        }

        /// <summary>
        /// 统一下单
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        private string UnifiedOrder(string xml, string tradetype = "JSAPI")
        {
            var url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            var result = WebClient.Request(url, null, xml.ToString());
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    string mch_id = res.Element("xml").Element("mch_id").Value;
                    string sign = res.Element("xml").Element("sign").Value;
                    string prepay_id = res.Element("xml").Element("prepay_id").Value;
                    string nonce_str = res.Element("xml").Element("nonce_str").Value;
                    if (tradetype == "JSAPI")
                        return H5PayResult(prepay_id);
                    else
                        return PrePayResult(prepay_id, mch_id, sign, nonce_str);
                }
                else
                {
                    throw new UserFriendlyException(res.Element("xml").Element("err_code_des").Value);
                    //return FailResult(res.Element("xml").Element("result_code").Value, res.Element("xml").Element("return_msg").Value);
                }
            }
            else
            {
                throw new UserFriendlyException(res.Element("xml").Element("return_msg").Value);
                //return FailResult(res.Element("xml").Element("return_code").Value, res.Element("xml").Element("return_msg").Value);
            }
        }

        public void Refund(string out_trade_no, string out_refund_no, decimal total_fee, decimal refund_fee,
            string refund_desc)
        {
            var url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appid", appid);
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("out_trade_no", out_trade_no);
            array.Add("out_refund_no", out_refund_no);
            array.Add("total_fee", ((int)(total_fee * 100)).ToString());
            array.Add("refund_fee", ((int)(refund_fee * 100)).ToString());
            array.Add("refund_desc", refund_desc);
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    //string refund_status = res.Element("xml").Elements().FirstOrDefault(x => x.Name.LocalName.StartsWith("refund_status_"))?.Value;
                    //var s = RefundResult(refund_status);
                    //return s;
                }
                else
                {
                    throw new UserFriendlyException(res.Element("xml").Element("err_code_des").Value);
                    //return FailResult(res.Element("xml").Element("result_code").Value, res.Element("xml").Element("return_msg").Value);
                }
            }
            else
            {
                throw new UserFriendlyException(res.Element("xml").Element("return_msg").Value);
                //return FailResult(res.Element("xml").Element("return_code").Value, res.Element("xml").Element("return_msg").Value);
            }
        }

        public RefunStatus RefundQuery(string out_refund_no)
        {
            var url = "https://api.mch.weixin.qq.com/pay/refundquery";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appid", appid);
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("out_refund_no", out_refund_no);
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    string refund_status = res.Element("xml").Elements().FirstOrDefault(x => x.Name.LocalName.StartsWith("refund_status_"))?.Value;
                    //var s = RefundResult(refund_status);
                    switch (refund_status)
                    {
                        case "SUCCESS":
                            return RefunStatus.SUCCESS;
                        case "REFUNDCLOSE":
                            return RefunStatus.REFUNDCLOSE;
                        case "PROCESSING":
                            return RefunStatus.PROCESSING;
                        case "CHANGE":
                            return RefunStatus.CHANGE;
                        default:
                            throw new UserFriendlyException(res.Element("xml").Element("return_msg").Value);
                    }
                }
                else
                {
                    throw new UserFriendlyException(res.Element("xml").Element("return_msg").Value);
                    //return FailResult(res.Element("xml").Element("result_code").Value, res.Element("xml").Element("return_msg").Value);
                }
            }
            else
            {
                throw new UserFriendlyException(res.Element("xml").Element("return_msg").Value);
                //return FailResult(res.Element("xml").Element("return_code").Value, res.Element("xml").Element("return_msg").Value);
            }
        }

        /// <summary>
        /// 提现到零钱
        /// </summary>
        /// <param name="partner_trade_no"></param>
        /// <param name="openid"></param>
        /// <param name="amount"></param>
        public bool PayBalance(string partner_trade_no, string openid, decimal amount)
        {
            var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("mch_appid", appid);
            array.Add("mchid", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("partner_trade_no", partner_trade_no);
            array.Add("openid", openid);
            array.Add("check_name", "NO_CHECK");
            array.Add("amount", ((int)(amount * 100)).ToString());
            array.Add("desc", "五行商城");
            array.Add("spbill_create_ip", server_ip);
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    return true;
                }
                else
                {
                    throw new Exception(res.Element("xml").Element("err_code_des").Value);
                }
            }
            else
            {
                throw new Exception(res.Element("xml").Element("return_msg").Value);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="partner_trade_no"></param>
        /// <returns></returns>
        public CashStatus PayBalanceResult(string partner_trade_no)
        {
            var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appid", appid);
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("partner_trade_no", partner_trade_no);
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    var status = res.Element("xml").Element("status").Value;
                    if (status == "SUCCESS")
                        return CashStatus.Success;
                    else if (status == "FAILED")
                        return CashStatus.Failed;
                    else if (status == "PROCESSING")
                        return CashStatus.Processing;
                }
                else
                {
                    throw new Exception(res.Element("xml").Element("err_code_des").Value);
                }
            }
            throw new Exception(res.Element("xml").Element("return_msg").Value);
        }

        //public void PayBank(string partner_trade_no, string bank_no, string true_name, string bank_code, decimal amount)
        //{
        //    var url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank";
        //    Dictionary<string, string> array = new Dictionary<string, string>();
        //    array.Add("mch_id", mch_id);
        //    array.Add("partner_trade_no", partner_trade_no);
        //    array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
        //    array.Add("enc_bank_no", RSAEncrypt(bank_no));
        //    array.Add("enc_true_name", RSAEncrypt(true_name));
        //    array.Add("bank_code", bank_code);
        //    array.Add("amount", ((int)(amount * 100)).ToString());
        //    array.Add("desc", "理财");
        //    var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
        //    var res = XDocument.Parse(result);
        //    //通信成功
        //    if (res.Element("xml").Element("return_code").Value == "SUCCESS")
        //    {
        //        if (res.Element("xml").Element("result_code").Value == "SUCCESS")
        //        {
        //        }
        //        else
        //        {
        //            throw new Exception(res.Element("xml").Element("err_code_des").Value);
        //        }
        //    }
        //    else
        //    {
        //        throw new Exception(res.Element("xml").Element("return_msg").Value);
        //    }
        //}

        public bool PayBank(string partner_trade_no, string bank_no, string true_name, string bank_code, decimal amount)
        {
            var url = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("mch_id", mch_id);
            array.Add("partner_trade_no", partner_trade_no);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("enc_bank_no", RSAEncrypt(bank_no));
            array.Add("enc_true_name", RSAEncrypt(true_name));
            array.Add("bank_code", bank_code);
            array.Add("amount", ((int)(amount * 100)).ToString());
            array.Add("desc", "提现");
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    return true;
                }
                else
                {
                    throw new Exception(res.Element("xml").Element("err_code_des").Value);
                }
            }
            else
            {
                throw new Exception(res.Element("xml").Element("return_msg").Value);
            }

            //var result = TenPayV3.PayBank(
            //    new TenPayV3PayBankRequestData(mch_id,
            //    Guid.NewGuid().ToString("N"),
            //    pub_key,
            //    partner_trade_no,
            //    en
            //    ),
            //    cert_path,
            //    mch_id);
        }

        private string RSAEncrypt(string content)
        {
            return RSAEncrypt(content, pub_key,"");
        }
        public static string RSAEncrypt(string content, string publicKeyPem, string charset)
        {
            string sPublicKeyPEM;
                sPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\r\n";
                sPublicKeyPEM += publicKeyPem;
                sPublicKeyPEM += "-----END PUBLIC KEY-----\r\n\r\n";
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.PersistKeyInCsp = false;
            RSACryptoServiceProviderExtension.LoadPublicKeyPEM(rsa, sPublicKeyPEM);
            if (string.IsNullOrEmpty(charset))
            {
                charset = DEFAULT_CHARSET;
            }
            byte[] data = Encoding.GetEncoding(charset).GetBytes(content);
            int maxBlockSize = rsa.KeySize / 8 - 11; //加密块最大长度限制
            if (data.Length <= maxBlockSize)
            {
                byte[] cipherbytes = rsa.Encrypt(data, true);
                return Convert.ToBase64String(cipherbytes);
            }
            MemoryStream plaiStream = new MemoryStream(data);
            MemoryStream crypStream = new MemoryStream();
            Byte[] buffer = new Byte[maxBlockSize];
            int blockSize = plaiStream.Read(buffer, 0, maxBlockSize);
            while (blockSize > 0)
            {
                Byte[] toEncrypt = new Byte[blockSize];
                Array.Copy(buffer, 0, toEncrypt, 0, blockSize);
                Byte[] cryptograph = rsa.Encrypt(toEncrypt, false);
                crypStream.Write(cryptograph, 0, cryptograph.Length);
                blockSize = plaiStream.Read(buffer, 0, maxBlockSize);
            }

            return Convert.ToBase64String(crypStream.ToArray(), Base64FormattingOptions.None);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="partner_trade_no"></param>
        /// <returns></returns>
        public CashStatus PayBankResult(string partner_trade_no)
        {
            var url = "https://api.mch.weixin.qq.com/mmpaysptrans/query_bank";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("partner_trade_no", partner_trade_no);
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    var status = res.Element("xml").Element("status").Value;
                    if (status == "SUCCESS")
                        return CashStatus.Success;
                    else if (status == "FAILED")
                        return CashStatus.Failed;
                    else if (status == "PROCESSING")
                        return CashStatus.Processing;
                    else if (status == "BANK_FAIL")
                        return CashStatus.BankFailed;
                }
                else
                {
                    throw new Exception(res.Element("xml").Element("err_code_des").Value);
                }
            }
            throw new Exception(res.Element("xml").Element("return_msg").Value);
        }

        public string GetPublicKey()
        {
            var url = "https://fraud.mch.weixin.qq.com/risk/getpublickey";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("mch_id", mch_id);
            array.Add("nonce_str", Guid.NewGuid().ToString("N"));//随机字符串
            array.Add("sign_type", "MD5");
            var result = WebClient.Request(url, null, SignToXml(array), cert_path, mch_id);
            var res = XDocument.Parse(result);
            //通信成功
            if (res.Element("xml").Element("return_code").Value == "SUCCESS")
            {
                if (res.Element("xml").Element("result_code").Value == "SUCCESS")
                {
                    var pub_key = res.Element("xml").Element("pub_key").Value;
                    return pub_key;
                }
                else
                {
                    throw new Exception(res.Element("xml").Element("err_code_des").Value);
                }
            }
            throw new Exception(res.Element("xml").Element("return_msg").Value);
        }

        private string MD5(string content)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] result = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(content));
            return BitConverter.ToString(result).Replace("-", "").ToUpper();
        }

        private string PrePayResult(string prepay_id, string mchid, string sign, string nonce_str)
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            var timeStamp = Convert.ToInt64(ts.TotalSeconds).ToString();
            var nonceStr = nonce_str;
            var package = "Sign=WXPay";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appid", appid);
            array.Add("partnerid", mchid);
            array.Add("prepayid", prepay_id);
            array.Add("timestamp", timeStamp);
            array.Add("noncestr", nonceStr);//随机字符串
            array.Add("package", package);
            array = array.OrderBy(i => i.Key).ToDictionary(i => i.Key, i => i.Value);
            var kepairArray = array.Where(i => !string.IsNullOrEmpty(i.Value)).Select(i => string.Format("{0}={1}", i.Key, i.Value));
            //加入密钥并生成签名字符串
            var signstr = string.Format("{0}&{1}={2}", string.Join("&", kepairArray), "key", api_key);
            //签名
            var paySign = MD5(signstr);
            array.Add("sign", paySign);
            return JsonConvert.SerializeObject(array);
        }

        private string H5PayResult(string prepay_id)
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            var timeStamp = Convert.ToInt64(ts.TotalSeconds).ToString();
            var nonceStr = Guid.NewGuid().ToString("N");
            var package = $"prepay_id={prepay_id}";
            var signType = "MD5";
            Dictionary<string, string> array = new Dictionary<string, string>();
            array.Add("appId", appid);
            array.Add("timeStamp", timeStamp);
            array.Add("nonceStr", nonceStr);//随机字符串
            array.Add("package", package);
            array.Add("signType", signType);
            //签名
            var paySign = Sign(array);
            return JsonConvert.SerializeObject(new
            {
                appId = appid,
                timeStamp,
                nonceStr,
                package,
                signType,
                paySign
            });
        }

        //private string RefundResult(string refund_status)
        //{
        //    return JsonConvert.SerializeObject(new
        //    {
        //        refund_status,
        //        code = "SUCCESS"
        //    });
        //}

        //private string FailResult(string code, string msg)
        //{
        //    return JsonConvert.SerializeObject(new
        //    {
        //        code,
        //        msg
        //    });
        //}
    }
}
